namespace createjs {

	export function getCanvas() {

	}
	export class StageGL extends Container {
		_preserveBuffer: any;
		_antialias: any;
		_transparent: any;
		_viewportWidth: number;
		_viewportHeight: number;
		_projectionMatrix: any;
		_webGLContext: any;
		_frameBuffer: any;
		_clearColor: { r: number; g: number; b: number; a: number; };
		_maxBatchVertexCount: number;
		_activeShader: any;
		_mainShader: any;
		_vertices: any;
		_vertexPositionBuffer: any;
		_uvs: any;
		_uvPositionBuffer: any;
		_indices: any;
		_textureIndexBuffer: any;
		_alphas: any;
		_alphaBuffer: any;
		_bufferTextureOutput: any;
		_bufferTextureConcat: any;
		_bufferTextureTemp: any;
		_batchTextureOutput: this;
		_batchTextureConcat: any;
		_batchTextureTemp: any;
		_builtShaders: {};
		_textureDictionary: any[];
		_textureIDs: {};
		_batchTextures: any[];
		_baseTextures: any[];
		_gpuTextureCount: number;
		_gpuTextureMax: number;
		_batchTextureCount: number;
		_lastTextureInsert: number;
		_renderMode: string;
		_immediateRender: boolean;
		_batchVertexCount: number;
		_batchID: number;
		_drawID: number;
		_lastTrackedCanvas: number;
		_cacheContainer: Container;

		autoClear: boolean;
		canvas: any;
		mouseX: number;
		mouseY: number;
		mouseInBounds: boolean;
		tickOnUpdate: boolean;
		batchReason: string;

		static isWebGLActive(ctx) {
			return ctx && ctx instanceof WebGLRenderingContext && typeof WebGLRenderingContext !== 'undefined';
		};

		static VERTEX_PROPERTY_COUNT = 6;

		static INDICIES_PER_CARD = 6;

		static DEFAULT_MAX_BATCH_SIZE = 10920;

		static WEBGL_MAX_INDEX_NUM = Math.pow(2, 16);

		static UV_RECT = { t: 1, l: 0, b: 0, r: 1 };

		static buildUVRects(spritesheet, target, onlyTarget) {
			if (!spritesheet || !spritesheet._frames) { return null; }
			if (target === undefined) { target = -1; }
			if (onlyTarget === undefined) { onlyTarget = false; }

			var start = (target !== -1 && onlyTarget) ? (target) : (0);
			var end = (target !== -1 && onlyTarget) ? (target + 1) : (spritesheet._frames.length);
			for (var i = start; i < end; i++) {
				var f = spritesheet._frames[i];
				if (f.uvRect || f.image.width <= 0 || f.image.height <= 0) { continue; }

				var r = f.rect;
				f.uvRect = {
					t: 1 - (r.y / f.image.height),
					l: r.x / f.image.width,
					b: 1 - ((r.y + r.height) / f.image.height),
					r: (r.x + r.width) / f.image.width
				};
			}

			return spritesheet._frames[(target !== -1) ? target : 0].uvRect || { t: 0, l: 0, b: 1, r: 1 };
		};

		static COVER_VERT = new Float32Array([
			-1, 1,		//TL
			1, 1,		//TR
			-1, -1,		//BL
			1, 1,		//TR
			1, -1,		//BR
			-1, -1		//BL
		]);

		static COVER_UV = new Float32Array([
			0, 1,		//TL
			1, 1,		//TR
			0, 0,		//BL
			1, 1,		//TR
			1, 0,		//BR
			0, 0		//BL
		]);




		static REGULAR_VARYING_HEADER = (
			"precision highp float;" +

			"varying vec2 vTextureCoord;" +
			"varying lowp float indexPicker;" +
			"varying lowp float alphaValue;"
		);

		static REGULAR_VERTEX_HEADER = (
			StageGL.REGULAR_VARYING_HEADER +
			"attribute vec2 vertexPosition;" +
			"attribute vec2 uvPosition;" +
			"attribute lowp float textureIndex;" +
			"attribute lowp float objectAlpha;" +
			"uniform mat4 pMatrix;"
		);
		static REGULAR_FRAGMENT_HEADER = (
			StageGL.REGULAR_VARYING_HEADER +
			"uniform sampler2D uSampler[{{count}}];"
		);

		static REGULAR_VERTEX_BODY = (
			"void main(void) {" +
			"gl_Position = pMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" +
			"alphaValue = objectAlpha;" +
			"indexPicker = textureIndex;" +
			"vTextureCoord = uvPosition;" +
			"}"
		);

		static REGULAR_FRAGMENT_BODY = (
			"void main(void) {" +
			"vec4 color = vec4(1.0, 0.0, 0.0, 1.0);" +

			"if (indexPicker <= 0.5) {" +
			"color = texture2D(uSampler[0], vTextureCoord);" +
			"{{alternates}}" +
			"}" +

			"gl_FragColor = vec4(color.rgb * alphaValue, color.a * alphaValue);" +
			"}"
		);

		static COVER_VARYING_HEADER = (
			"precision highp float;" +	//this is usually essential for filter math

			"varying vec2 vTextureCoord;"
		);

		static COVER_VERTEX_HEADER = (
			StageGL.COVER_VARYING_HEADER +
			"attribute vec2 vertexPosition;" +
			"attribute vec2 uvPosition;"
		);

		static COVER_FRAGMENT_HEADER = (
			StageGL.COVER_VARYING_HEADER +
			"uniform sampler2D uSampler;"
		);

		static COVER_VERTEX_BODY = (
			"void main(void) {" +
			"gl_Position = vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" +
			"vTextureCoord = uvPosition;" +
			"}"
		);

		static COVER_FRAGMENT_BODY = (
			"void main(void) {" +
			"gl_FragColor = texture2D(uSampler, vTextureCoord);" +
			"}"
		);

		static BLEND_FRAGMENT_SIMPLE = (
			"uniform sampler2D uMixSampler;" +
			"void main(void) {" +
			"vec4 src = texture2D(uMixSampler, vTextureCoord);" +
			"vec4 dst = texture2D(uSampler, vTextureCoord);"
			// note this is an open bracket on main!
		);

		static BLEND_FRAGMENT_COMPLEX = (
			StageGL.BLEND_FRAGMENT_SIMPLE +
			"vec3 srcClr = min(src.rgb/src.a, 1.0);" +
			"vec3 dstClr = min(dst.rgb/dst.a, 1.0);" +

			"float totalAlpha = min(1.0 - (1.0-dst.a) * (1.0-src.a), 1.0);" +
			"float srcFactor = min(max(src.a - dst.a, 0.0) / totalAlpha, 1.0);" +
			"float dstFactor = min(max(dst.a - src.a, 0.0) / totalAlpha, 1.0);" +
			"float mixFactor = max(max(1.0 - srcFactor, 0.0) - dstFactor, 0.0);" +

			"gl_FragColor = vec4(" +
			"(" +
			"srcFactor * srcClr +" +
			"dstFactor * dstClr +" +
			"mixFactor * vec3("
			// this should be closed with the cap!
		);

		static BLEND_FRAGMENT_COMPLEX_CAP = (
			")" +
			") * totalAlpha, totalAlpha" +
			");" +
			"}"
		);

		static BLEND_FRAGMENT_OVERLAY_UTIL = (
			"float overlay(float a, float b) {" +
			"if(a < 0.5) { return 2.0 * a * b; }" +
			"return 1.0 - 2.0 * (1.0-a) * (1.0-b);" +
			"}"
		);

		static BLEND_FRAGMENT_HSL_UTIL = (
			"float getLum(vec3 c) { return 0.299*c.r + 0.589*c.g + 0.109*c.b; }" +
			"float getSat(vec3 c) { return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b); }" +
			"vec3 clipHSL(vec3 c) {" +
			"float lum = getLum(c);" +
			"float n = min(min(c.r, c.g), c.b);" +
			"float x = max(max(c.r, c.g), c.b);" +
			"if(n < 0.0){ c = lum + (((c - lum) * lum) / (lum - n)); }" +
			"if(x > 1.0){ c = lum + (((c - lum) * (1.0 - lum)) / (x - lum)); }" +
			"return clamp(c, 0.0, 1.0);" +
			"}" +
			"vec3 setLum(vec3 c, float lum) {" +
			"return clipHSL(c + (lum - getLum(c)));" +
			"}" +
			"vec3 setSat(vec3 c, float val) {" +
			"vec3 result = vec3(0.0);" +
			"float minVal = min(min(c.r, c.g), c.b);" +
			"float maxVal = max(max(c.r, c.g), c.b);" +
			"vec3 minMask = vec3(c.r == minVal, c.g == minVal, c.b == minVal);" +
			"vec3 maxMask = vec3(c.r == maxVal, c.g == maxVal, c.b == maxVal);" +
			"vec3 midMask = 1.0 - min(minMask+maxMask, 1.0);" +
			"float midVal = (c*midMask).r + (c*midMask).g + (c*midMask).b;" +
			"if(maxVal > minVal) {" +
			"result = midMask * min( ((midVal - minVal) * val) / (maxVal - minVal), 1.0);" +
			"result += maxMask * val;" +
			"}" +
			"return result;" +
			"}"
		);

		static BLEND_SOURCES = {
			"source-over": { // empty object verifies it as a blend mode, but default values handle actual settings
				//eqRGB: "FUNC_ADD",						eqA: "FUNC_ADD"
				//srcRGB: "ONE",							srcA: "ONE"
				//dstRGB: "ONE_MINUS_SRC_ALPHA",			dstA: "ONE_MINUS_SRC_ALPHA"
			},
			"source-in": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"gl_FragColor = vec4(src.rgb * dst.a, src.a * dst.a);" +
					"}")
			},
			"source-in_cheap": {
				srcRGB: "DST_ALPHA", srcA: "ZERO",
				dstRGB: "ZERO", dstA: "SRC_ALPHA"
			},
			"source-out": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"gl_FragColor = vec4(src.rgb * (1.0 - dst.a), src.a - dst.a);" +
					"}")
			},
			"source-out_cheap": {
				eqA: "FUNC_SUBTRACT",
				srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE",
				dstRGB: "ZERO", dstA: "SRC_ALPHA"
			},
			"source-atop": {
				srcRGB: "DST_ALPHA", srcA: "ZERO",
				dstRGB: "ONE_MINUS_SRC_ALPHA", dstA: "ONE"
			},
			"destination-over": {
				srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE_MINUS_DST_ALPHA",
				dstRGB: "ONE", dstA: "ONE"
			},
			"destination-in": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"gl_FragColor = vec4(dst.rgb * src.a, src.a * dst.a);" +
					"}")
			},
			"destination-in_cheap": {
				srcRGB: "ZERO", srcA: "DST_ALPHA",
				dstRGB: "SRC_ALPHA", dstA: "ZERO"
			},
			"destination-out": {
				eqA: "FUNC_REVERSE_SUBTRACT",
				srcRGB: "ZERO", srcA: "DST_ALPHA",
				dstRGB: "ONE_MINUS_SRC_ALPHA", dstA: "ONE"
			},
			"destination-atop": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"gl_FragColor = vec4(dst.rgb * src.a + src.rgb * (1.0 - dst.a), src.a);" +
					"}")
			},
			"destination-atop_cheap": {
				srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE",
				dstRGB: "SRC_ALPHA", dstA: "ZERO"
			},
			"copy": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"gl_FragColor = vec4(src.rgb, src.a);" +
					"}")
			},
			"copy_cheap": {
				dstRGB: "ZERO", dstA: "ZERO"
			},
			"xor": {
				shader: (StageGL.BLEND_FRAGMENT_SIMPLE +
					"float omSRC = (1.0 - src.a);" +
					"float omDST = (1.0 - dst.a);" +
					"gl_FragColor = vec4(src.rgb * omDST + dst.rgb * omSRC, src.a * omDST + dst.a * omSRC);"
					+ "}")
			},

			"multiply": { // this has to be complex to handle retention of both dst and src in non mixed scenarios
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"srcClr * dstClr"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"multiply_cheap": { // NEW, handles retention of src data incorrectly when no dst data present
				srcRGB: "ONE_MINUS_DST_ALPHA", srcA: "ONE",
				dstRGB: "SRC_COLOR", dstA: "ONE"
			},
			"screen": {
				srcRGB: "ONE", srcA: "ONE",
				dstRGB: "ONE_MINUS_SRC_COLOR", dstA: "ONE_MINUS_SRC_ALPHA"
			},
			"lighter": {
				dstRGB: "ONE", dstA: "ONE"
			},
			"lighten": { //WebGL 2.0 can optimize this
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"max(srcClr, dstClr)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"darken": { //WebGL 2.0 can optimize this
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"min(srcClr, dstClr)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},

			"overlay": {
				shader: (StageGL.BLEND_FRAGMENT_OVERLAY_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"overlay(dstClr.r,srcClr.r), overlay(dstClr.g,srcClr.g), overlay(dstClr.b,srcClr.b)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"hard-light": {
				shader: (StageGL.BLEND_FRAGMENT_OVERLAY_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"overlay(srcClr.r,dstClr.r), overlay(srcClr.g,dstClr.g), overlay(srcClr.b,dstClr.b)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"soft-light": {
				shader: (
					"float softcurve(float a) {" +
					"if(a > 0.25) { return sqrt(a); }" +
					"return ((16.0 * a - 12.0) * a + 4.0) * a;" +
					"}" +
					"float softmix(float a, float b) {" +
					"if(b <= 0.5) { return a - (1.0 - 2.0*b) * a * (1.0 - a); }" +
					"return a + (2.0 * b - 1.0) * (softcurve(a) - a);" +
					"}" + StageGL.BLEND_FRAGMENT_COMPLEX +
					"softmix(dstClr.r,srcClr.r), softmix(dstClr.g,srcClr.g), softmix(dstClr.b,srcClr.b)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"color-dodge": {
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"clamp(dstClr / (1.0 - srcClr), 0.0, 1.0)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"color-burn": {
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"1.0 - clamp((1.0 - smoothstep(0.0035, 0.9955, dstClr)) / smoothstep(0.0035, 0.9955, srcClr), 0.0, 1.0)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"difference": { // do this to match visible results in browsers
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"abs(src.rgb - dstClr)"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"exclusion": { // do this to match visible results in browsers
				shader: (StageGL.BLEND_FRAGMENT_COMPLEX +
					"dstClr + src.rgb - 2.0 * src.rgb * dstClr"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},

			"hue": {
				shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"setLum(setSat(srcClr, getSat(dstClr)), getLum(dstClr))"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"saturation": {
				shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"setLum(setSat(dstClr, getSat(srcClr)), getLum(dstClr))"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"color": {
				shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"setLum(srcClr, getLum(dstClr))"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			},
			"luminosity": {
				shader: (StageGL.BLEND_FRAGMENT_HSL_UTIL + StageGL.BLEND_FRAGMENT_COMPLEX +
					"setLum(dstClr, getLum(srcClr))"
					+ StageGL.BLEND_FRAGMENT_COMPLEX_CAP)
			}
		};

		constructor(canvas, options?) {
			super();

			this.canvas = (typeof canvas == "string") ? document.getElementById(canvas) : canvas;
			this.autoClear = true;
			this.tickOnUpdate = true;

			var transparent, antialias, preserveBuffer;
			if (options !== undefined) {
				if (typeof options !== "object") {
					throw ("Invalid options object");
				}
				transparent = options.transparent;
				antialias = options.antialias;
				preserveBuffer = options.preserveBuffer;
			}


			this._preserveBuffer = preserveBuffer || false;
			this._antialias = antialias || false;
			this._transparent = transparent || false;

			this._viewportWidth = 0;
			this._viewportHeight = 0;

			this._projectionMatrix = null;
			this._webGLContext = null;
			this._frameBuffer = null;
			this._clearColor = { r: 0.50, g: 0.50, b: 0.50, a: 0.00 };
			this._maxBatchVertexCount = StageGL.DEFAULT_MAX_BATCH_SIZE * StageGL.INDICIES_PER_CARD;

			this._activeShader = null;
			this._mainShader = null;

			this._vertices = null;
			this._vertexPositionBuffer = null;
			this._uvs = null;
			this._uvPositionBuffer = null;
			this._indices = null;
			this._textureIndexBuffer = null;
			this._alphas = null;
			this._alphaBuffer = null;

			this._bufferTextureOutput = null;
			this._bufferTextureConcat = null;

			this._bufferTextureTemp = null;
			this._batchTextureOutput = this;
			this._batchTextureConcat = null;
			this._batchTextureTemp = null;
			this._builtShaders = {};
			this._textureDictionary = [];
			this._textureIDs = {};
			this._batchTextures = [];
			this._baseTextures = [];
			this._gpuTextureCount = 8;
			this._gpuTextureMax = 8;
			this._batchTextureCount = 0;
			this._lastTextureInsert = -1;
			this._renderMode = "";

			this._immediateRender = false;

			this._batchVertexCount = 0;

			this._batchID = 0;

			this._drawID = 0;

			this._lastTrackedCanvas = -1;

			this._cacheContainer = new createjs.Container();

			// and begin
			this._initializeWebGL();

			// these settings require everything to be ready
			if (options !== undefined) {
				options.clearColor !== undefined && this.setClearColor(options.clearColor);
			}
		}

		get isWebGL() {
			return !!this._webGLContext;
		}

		handleEvent(evt) {
			if (evt.type == "tick") {
				this.update();
			}
		};

		tick() {
			if (!this.tickEnabled) {
				return;
			}
			var evtObj = new createjs.Event("tick");
			this._tick(evtObj);
		};

		_initializeWebGL() {
			if (!this._webGLContext || this._webGLContext.canvas !== this.canvas) {
				// A context hasn't been defined yet,
				// OR the defined context belongs to a different canvas, so reinitialize.

				// defaults and options
				var options = {
					depth: false, // nothing has depth
					stencil: false, // while there's uses for this, we're not using any yet
					premultipliedAlpha: this._transparent, // this is complicated, trust it

					alpha: this._transparent,
					antialias: this._antialias,
					preserveDrawingBuffer: this._preserveBuffer
				};

				var gl = this._webGLContext = this._fetchWebGLContext(this.canvas, options);
				if (!gl) {
					return null;
				}

				gl.disable(gl.DEPTH_TEST);
				gl.depthMask(false);
				gl.enable(gl.BLEND);
				gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
				gl.clearColor(0.0, 0.0, 0.0, 0);
				gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
				gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

				this._createBuffers();
				this._initMaterials();
				this._updateRenderMode("source-over");

				this.updateViewport(this.canvas.width, this.canvas.height);

				this.canvas._invalid = true;
			}
			return this._webGLContext;
		};

		// public methods:
		// Docced in superclass
		update() {
			if (!this.canvas) {
				return;
			}
			if (this.tickOnUpdate) {
				this.tick();
			}

			this.draw();
		};

		// Docced in superclass
		clear() {
			if (!this.canvas) { return; }

			var gl = this._webGLContext;

			gl.bindFramebuffer(gl.FRAMEBUFFER, null);
			this._clearFrameBuffer(this._transparent ? this._clearColor.a : 1);
		};

		draw() {
			var gl = this._webGLContext;

			var storeBatchOutput = this._batchTextureOutput;
			var storeBatchConcat = this._batchTextureConcat;
			var storeBatchTemp = this._batchTextureTemp;

			// Use WebGL
			this._batchVertexCount = 0;
			this._drawID++;

			this._batchTextureOutput = this;
			if (this.autoClear) {
				this.clear();
			}

			this._updateRenderMode("source-over");
			this._drawContent(this);

			// batches may generate or swap around textures. To be sure we capture them, store them back into buffer
			this._bufferTextureOutput = this._batchTextureOutput;
			this._bufferTextureConcat = this._batchTextureConcat;
			this._bufferTextureTemp = this._batchTextureTemp;

			this._batchTextureOutput = storeBatchOutput;
			this._batchTextureConcat = storeBatchConcat;
			this._batchTextureTemp = storeBatchTemp;

			if ( !(this._drawID % 600)) {
				this.purgeTextures(600);
			}

			return true;
		};

		cacheDraw(target, manager) {
			// 2D context fallback
			if (!StageGL.isWebGLActive(this._webGLContext)) {
				return false;
			}

			var storeBatchOutput = this._batchTextureOutput;
			var storeBatchConcat = this._batchTextureConcat;
			var storeBatchTemp = this._batchTextureTemp;

			var filterCount = manager._filterCount, filtersLeft = filterCount;
			var backupWidth = this._viewportWidth, backupHeight = this._viewportHeight;
			this._updateDrawingSurface(manager._drawWidth, manager._drawHeight);

			this._batchTextureOutput = (manager._filterCount % 2) ? manager._bufferTextureConcat : manager._bufferTextureOutput;
			this._batchTextureConcat = (manager._filterCount % 2) ? manager._bufferTextureOutput : manager._bufferTextureConcat;
			this._batchTextureTemp = manager._bufferTextureTemp;

			var container = this._cacheContainer;
			container.children = [target];
			container.transformMatrix = this._alignTargetToCache(target, manager);

			this._updateRenderMode("source-over");
			this._drawContent(container);

			if (manager._cacheCanvas !== ((manager._filterCount % 2) ? this._batchTextureConcat : this._batchTextureOutput)) {
				// pre filter pass to align output, may of become misaligned due to composite operations
				filtersLeft++;
			}

			while (filtersLeft) { //warning: pay attention to where filtersLeft is modified, this is a micro-optimization
				var filter = manager._getGLFilter(filterCount - (filtersLeft--));
				var swap = this._batchTextureConcat;
				this._batchTextureConcat = this._batchTextureOutput;
				this._batchTextureOutput = swap;
				this.batchReason = "filterPass";
				this._drawCover(this._batchTextureOutput._frameBuffer, this._batchTextureConcat, filter);
			}

			manager._bufferTextureOutput = this._batchTextureOutput;
			manager._bufferTextureConcat = this._batchTextureConcat;
			manager._bufferTextureTemp = this._batchTextureTemp;

			this._batchTextureOutput = storeBatchOutput;
			this._batchTextureConcat = storeBatchConcat;
			this._batchTextureTemp = storeBatchTemp;

			this._updateDrawingSurface(backupWidth, backupHeight);
			return true;
		};


		releaseTexture(item, safe?) {
			var i, l;
			if (!item) { return; }

			// this is a container object
			if (item.children) {
				for (i = 0, l = item.children.length; i < l; i++) {
					this.releaseTexture(item.children[i], safe);
				}
			}

			// this has a cache canvas
			if (item.cacheCanvas) {
				item.uncache();
			}

			var foundImage = undefined;
			if (item._storeID !== undefined) {
				// this is a texture itself
				if (item === this._textureDictionary[item._storeID]) {
					this._killTextureObject(item);
					item._storeID = undefined;
					return;
				}

				// this is an image or canvas
				foundImage = item;
			} else if (item._webGLRenderStyle === 1) {
				// this is a SpriteSheet, we can't tell which image we used from the list easily so remove them all!
				for (i = 0, l = item.spriteSheet._images.length; i < l; i++) {
					this.releaseTexture(item.spriteSheet._images[i], safe);
				}
				return;
			}

			// did we find anything
			if (foundImage === undefined) {
				console.log("No associated texture found on release");
				return;
			}

			// remove it
			var texture = this._textureDictionary[foundImage._storeID];
			if (safe) {
				var data = texture._imageData;
				var index = data.indexOf(foundImage);
				if (index >= 0) { data.splice(index, 1); }
				foundImage._storeID = undefined;
				if (data.length === 0) { this._killTextureObject(texture); }
			} else {
				this._killTextureObject(texture);
			}
		};


		purgeTextures(count) {
			if (!(count >= 0)) { count = 100; }

			var dict = this._textureDictionary;
			var l = dict.length;
			for (var i = 0; i < l; i++) {
				var data, texture = dict[i];
				if (!texture || !(data = texture._imageData)) { continue; }

				for (var j = 0; j < data.length; j++) {
					var item = data[j];
					if (item._drawID + count <= this._drawID) {
						item._storeID = undefined;
						data.splice(j, 1);
						j--;
					}
				}

				if (!data.length) {
					this._killTextureObject(texture);
				}
			}
		};

		updateViewport(width, height) {
			width = Math.abs(width | 0) || 1;
			height = Math.abs(height | 0) || 1;

			this._updateDrawingSurface(width, height);

			if (this._bufferTextureOutput !== this && this._bufferTextureOutput !== null) {
				this.resizeTexture(this._bufferTextureOutput, this._viewportWidth, this._viewportHeight);
			}
			if (this._bufferTextureConcat !== null) {
				this.resizeTexture(this._bufferTextureConcat, this._viewportWidth, this._viewportHeight);
			}
			if (this._bufferTextureTemp !== null) {
				this.resizeTexture(this._bufferTextureTemp, this._viewportWidth, this._viewportHeight);
			}
		};


		getFilterShader(filter) {
			if (!filter) { filter = this; }

			var gl = this._webGLContext;
			var targetShader = this._activeShader;

			if (filter._builtShader) {
				targetShader = filter._builtShader;
				if (filter.shaderParamSetup) {
					gl.useProgram(targetShader);
					filter.shaderParamSetup(gl, this, targetShader);
				}
			} else {
				try {
					targetShader = this._fetchShaderProgram(
						true, filter.VTX_SHADER_BODY, filter.FRAG_SHADER_BODY,
						filter.shaderParamSetup && filter.shaderParamSetubind(filter)
					);
					filter._builtShader = targetShader;
					targetShader._name = filter.toString();
				} catch (e) {
					console && console.log("SHADER SWITCH FAILURE", e);
				}
			}
			return targetShader;
		};

		getBaseTexture(w?, h?) {
			var width = Math.ceil(w > 0 ? w : 1) || 1;
			var height = Math.ceil(h > 0 ? h : 1) || 1;

			var gl = this._webGLContext;
			var texture = gl.createTexture();
			this.resizeTexture(texture, width, height);
			this.setTextureParams(gl, false);

			return texture;
		};


		resizeTexture(texture, width, height) {
			if (texture.width === width && texture.height === height) { return; }

			var gl = this._webGLContext;
			gl.bindTexture(gl.TEXTURE_2D, texture);
			gl.texImage2D(
				gl.TEXTURE_2D,				// target
				0,							// level of detail
				gl.RGBA,					// internal format
				width, height, 0,			// width, height, border (only for array/null sourced textures)
				gl.RGBA,					// format (match internal format)
				gl.UNSIGNED_BYTE,			// type of texture(pixel color depth)
				null						// image data, we can do null because we're doing array data
			);

			// set its width and height for spoofing as an image and tracking
			texture.width = width;
			texture.height = height;
		};

		getRenderBufferTexture(w, h) {
			var gl = this._webGLContext;

			var renderTexture = this.getBaseTexture(w, h);
			if (!renderTexture) { return null; }

			var frameBuffer = gl.createFramebuffer();
			if (!frameBuffer) { return null; }

			// set its width and height for spoofing as an image and tracking
			renderTexture.width = w;
			renderTexture.height = h;

			// attach frame buffer to texture and provide cross links to look up each other
			gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
			gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, renderTexture, 0);
			frameBuffer._renderTexture = renderTexture;
			renderTexture._frameBuffer = frameBuffer;

			// these keep track of themselves simply to reduce complexity of some lookup code
			renderTexture._storeID = this._textureDictionary.length;
			this._textureDictionary[renderTexture._storeID] = renderTexture;

			gl.bindFramebuffer(gl.FRAMEBUFFER, null);
			return renderTexture;
		};

		setTextureParams(gl, isPOT?) {
			if (isPOT && this._antialias) {
				//non POT linear works in some devices, but performance is NOT good, investigate
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
			} else {
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
			}
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		};


		setClearColor(color) {
			var r, g, b, a, output;

			if (typeof color === "string") {
				if (color.indexOf("#") === 0) {
					if (color.length === 4) {
						color = "#" + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2) + color.charAt(3) + color.charAt(3)
					}
					r = Number("0x" + color.slice(1, 3)) / 255;
					g = Number("0x" + color.slice(3, 5)) / 255;
					b = Number("0x" + color.slice(5, 7)) / 255;
					a = Number("0x" + color.slice(7, 9)) / 255;
				} else if (color.indexOf("rgba(") === 0) {
					output = color.slice(5, -1).split(",");
					r = Number(output[0]) / 255;
					g = Number(output[1]) / 255;
					b = Number(output[2]) / 255;
					a = Number(output[3]);
				}
			} else {	// >>> is an unsigned shift which is what we want as 0x80000000 and up are negative values
				r = ((color & 0xFF000000) >>> 24) / 255;
				g = ((color & 0x00FF0000) >>> 16) / 255;
				b = ((color & 0x0000FF00) >>> 8) / 255;
				a = (color & 0x000000FF) / 255;
			}

			this._clearColor.r = r || 0;
			this._clearColor.g = g || 0;
			this._clearColor.b = b || 0;
			this._clearColor.a = a || 0;
		};

		// Docced in subclass
		toString() {
			return "[StageGL (name=" + this.name + ")]";
		};

		_updateDrawingSurface(w, h) {
			this._viewportWidth = w;
			this._viewportHeight = h;

			this._webGLContext.viewport(0, 0, this._viewportWidth, this._viewportHeight);

			// WebGL works with a -1,1 space on its screen. It also follows Y-Up
			// we need to flip the y, scale and then translate the co-ordinates to match this
			// additionally we offset into they Y so the polygons are inside the camera's "clipping" plane
			this._projectionMatrix = new Float32Array([
				2 / w, 0, 0, 0,
				0, -2 / h, 0, 0,
				0, 0, 1, 0,
				-1, 1, 0, 1
			]);
		};

		_getSafeTexture(w?, h?) {
			var texture = this.getBaseTexture(w, h);

			if (!texture) {
				var msg = "Problem creating texture, possible cause: using too much VRAM, please try releasing texture memory";
				(console.error && console.error(msg)) || console.log(msg);

				texture = this._baseTextures[0];
			}

			return texture;
		};

		_clearFrameBuffer(alpha) {
			var gl = this._webGLContext;
			var cc = this._clearColor;

			if (alpha > 0) { alpha = 1; }
			if (alpha < 0) { alpha = 0; }

			// Use WebGL settings; adjust for pre multiplied alpha appropriate to scenario
			gl.clearColor(cc.r * alpha, cc.g * alpha, cc.b * alpha, alpha);
			gl.clear(gl.COLOR_BUFFER_BIT);
			gl.clearColor(0, 0, 0, 0);
		};

		_fetchWebGLContext(canvas, options) {
			var gl;

			try {
				gl = canvas.getContext("webgl", options);
			} catch (e) {
				// don't do anything in catch, null check will handle it
			}

			if (!gl) {
				var msg = "Could not initialize WebGL";
				console.error ? console.error(msg) : console.log(msg);
			} else {
				gl.viewportWidth = canvas.width;
				gl.viewportHeight = canvas.height;
			}

			return gl;
		};


		_fetchShaderProgram(coverShader, customVTX?, customFRAG?, shaderParamSetup?) {
			var gl = this._webGLContext;

			gl.useProgram(null);		// safety to avoid collisions

			// build the correct shader string out of the right headers and bodies
			var targetFrag, targetVtx;
			if (coverShader) {
				targetVtx = StageGL.COVER_VERTEX_HEADER + (customVTX || StageGL.COVER_VERTEX_BODY);
				targetFrag = StageGL.COVER_FRAGMENT_HEADER + (customFRAG || StageGL.COVER_FRAGMENT_BODY);
			} else {
				targetVtx = StageGL.REGULAR_VERTEX_HEADER + (customVTX || StageGL.REGULAR_VERTEX_BODY);
				targetFrag = StageGL.REGULAR_FRAGMENT_HEADER + (customFRAG || StageGL.REGULAR_FRAGMENT_BODY);
			}

			// create the separate vars
			var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, targetVtx);
			var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, targetFrag);

			// link them together
			var shaderProgram = gl.createProgram();
			gl.attachShader(shaderProgram, vertexShader);
			gl.attachShader(shaderProgram, fragmentShader);
			gl.linkProgram(shaderProgram);

			// check compile status
			if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
				gl.useProgram(this._activeShader);
				throw gl.getProgramInfoLog(shaderProgram);
			}

			// set up the parameters on the shader
			gl.useProgram(shaderProgram);

			// get the places in memory the shader is stored so we can feed information into them
			// then save it off on the shader because it's so tied to the shader itself
			shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPosition");
			gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

			shaderProgram.uvPositionAttribute = gl.getAttribLocation(shaderProgram, "uvPosition");
			gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute);

			if (coverShader) {
				shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
				gl.uniform1i(shaderProgram.samplerUniform, 0);

				// if there's some custom attributes be sure to hook them up
				if (shaderParamSetup) {
					shaderParamSetup(gl, this, shaderProgram);
				}
			} else {
				shaderProgram.textureIndexAttribute = gl.getAttribLocation(shaderProgram, "textureIndex");
				gl.enableVertexAttribArray(shaderProgram.textureIndexAttribute);

				shaderProgram.alphaAttribute = gl.getAttribLocation(shaderProgram, "objectAlpha");
				gl.enableVertexAttribArray(shaderProgram.alphaAttribute);

				var samplers = [];
				for (var i = 0; i < this._gpuTextureCount; i++) {
					samplers[i] = i;
				}
				shaderProgram.samplerData = samplers;

				shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
				gl.uniform1iv(shaderProgram.samplerUniform, shaderProgram.samplerData);

				shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "pMatrix");
			}

			shaderProgram._type = coverShader ? "cover" : "batch";

			gl.useProgram(this._activeShader);
			return shaderProgram;
		};

		_createShader(gl, type, str) {
			var textureCount = this._batchTextureCount;

			// inject the static number
			str = str.replace(/\{\{count}}/g, textureCount);

			if (type === gl.FRAGMENT_SHADER) {
				// resolve issue with no dynamic samplers by creating correct samplers in if else chain
				// TODO: WebGL 2.0 does not need this support
				var insert = "";
				for (var i = 1; i < textureCount; i++) {
					insert += "} else if (indexPicker <= " + i + ".5) { color = texture2D(uSampler[" + i + "], vTextureCoord);";
				}
				str = str.replace(/\{\{alternates}}/g, insert);
			}

			// actually compile the shader
			var shader = gl.createShader(type);
			gl.shaderSource(shader, str);
			gl.compileShader(shader);

			// check compile status
			if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
				throw gl.getShaderInfoLog(shader);
			}

			return shader;
		};

		_createBuffers() {
			var gl = this._webGLContext;
			var groupCount = this._maxBatchVertexCount;
			var groupSize, i, l;

			// INFO:
			// all buffers are created using this pattern
			// create a WebGL buffer
			// attach it to context
			// figure out how many parts it has to an entry
			// fill it with empty data to reserve the memory
			// attach the empty data to the GPU
			// track the sizes on the buffer object

			// INFO:
			// a single buffer may be optimal in some situations and would be approached like this,
			// currently not implemented due to lack of need and potential complications with drawCover

			// var vertexBuffer = this._vertexBuffer = gl.createBuffer();
			// gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
			// groupSize = 2 + 2 + 1 + 1; //{x,y}, {u,v}, index, alpha
			// var vertexData = this._vertexData = new Float32Array(groupCount * groupSize);
			// for (i=0; i<vertexData.length; i+=groupSize) {
			// 	vertexData[i+0] = vertexData[i+1] = 0;
			// 	vertexData[i+2] = vertexData[i+3] = 0.5;
			// 	vertexData[i+4] = 0;
			// 	vertexData[i+5] = 1;
			// }
			// vertexBuffer.itemSize = groupSize;
			// vertexBuffer.numItems = groupCount;
			// TODO benchmark and test using unified buffer

			// the actual position information
			var vertexPositionBuffer = this._vertexPositionBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
			groupSize = 2;
			var vertices = this._vertices = new Float32Array(groupCount * groupSize);
			for (i = 0, l = vertices.length; i < l; i += groupSize) { vertices[i] = vertices[i + 1] = 0; }
			gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
			vertexPositionBuffer.itemSize = groupSize;
			vertexPositionBuffer.numItems = groupCount;

			// where on the texture it gets its information
			var uvPositionBuffer = this._uvPositionBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
			groupSize = 2;
			var uvs = this._uvs = new Float32Array(groupCount * groupSize);
			for (i = 0, l = uvs.length; i < l; i += groupSize) { uvs[i] = uvs[i + 1] = 0; }
			gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.DYNAMIC_DRAW);
			uvPositionBuffer.itemSize = groupSize;
			uvPositionBuffer.numItems = groupCount;

			// what texture it should use
			var textureIndexBuffer = this._textureIndexBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer);
			groupSize = 1;
			var indices = this._indices = new Float32Array(groupCount * groupSize);
			for (i = 0, l = indices.length; i < l; i++) { indices[i] = 0; }
			gl.bufferData(gl.ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW);
			textureIndexBuffer.itemSize = groupSize;
			textureIndexBuffer.numItems = groupCount;

			// what alpha it should have
			var alphaBuffer = this._alphaBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer);
			groupSize = 1;
			var alphas = this._alphas = new Float32Array(groupCount * groupSize);
			for (i = 0, l = alphas.length; i < l; i++) { alphas[i] = 1; }
			gl.bufferData(gl.ARRAY_BUFFER, alphas, gl.DYNAMIC_DRAW);
			alphaBuffer.itemSize = groupSize;
			alphaBuffer.numItems = groupCount;
		};

		_initMaterials() {
			var gl = this._webGLContext;

			// reset counters
			this._lastTextureInsert = -1;

			// clear containers
			this._textureDictionary = [];
			this._textureIDs = {};
			this._baseTextures = [];
			this._batchTextures = [];

			this._gpuTextureCount = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); // this is what we can draw with
			this._gpuTextureMax = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); // this could be higher

			this._batchTextureCount = this._gpuTextureCount;
			var success = false;
			while (!success) {
				try {
					this._activeShader = this._fetchShaderProgram(false);
					success = true;
				} catch (e) {
					if (this._batchTextureCount <= 1) { throw "Cannot compile shader " + e; }
					this._batchTextureCount = (this._batchTextureCount / 2) | 0;

					console.log("Reducing possible texture count due to errors: " + this._batchTextureCount);
				}
			}

			this._mainShader = this._activeShader;
			this._mainShader._name = "main";

			// fill in blanks as it helps the renderer be stable while textures are loading and reduces need for safety code
			var texture = this.getBaseTexture();
			if (!texture) {
				throw "Problems creating basic textures, known causes include using too much VRAM by not releasing WebGL texture instances";
			} else {
				texture._storeID = -1;
			}
			for (var i = 0; i < this._batchTextureCount; i++) {
				this._baseTextures[i] = this._batchTextures[i] = texture;
			}
		};

		_loadTextureImage(gl, image) {
			var srcPath, texture, msg;
			if (image instanceof Image && image.src) {
				srcPath = image.src;
			} else if (image instanceof HTMLCanvasElement) {
				(image as any)._isCanvas = true; //canvases are already loaded and assumed unique so note that
				srcPath = "canvas_" + (++this._lastTrackedCanvas);
			} else {
				msg = "Invalid image provided as source. Please ensure source is a correct DOM element.";
				(console.error && console.error(msg, image)) || console.log(msg, image);
				return;
			}

			// create the texture lookup and texture
			var storeID = this._textureIDs[srcPath];
			if (storeID === undefined) {
				this._textureIDs[srcPath] = storeID = this._textureDictionary.length;
				(image as any)._storeID = storeID;
				(image as any)._invalid = true;
				texture = this._getSafeTexture();
				this._textureDictionary[storeID] = texture;
			} else {
				(image as any)._storeID = storeID;
				texture = this._textureDictionary[storeID];
			}

			// allow the texture to track its references for cleanup, if it's not an error ref
			if (texture._storeID !== -1) {
				texture._storeID = storeID;
				if (texture._imageData) {
					texture._imageData.push(image);
				} else {
					texture._imageData = [image];
				}
			}

			// insert texture into batch
			this._insertTextureInBatch(gl, texture);

			return texture;
		};

		_updateTextureImageData(gl, image) {
			// the image isn't loaded and isn't ready to be updated, because we don't set the invalid flag we should try again later
			if (!(image.complete || image._isCanvas || image.naturalWidth)) {
				return;
			}

			// the bitwise & is intentional, cheap exponent 2 check
			var isNPOT = (image.width & image.width - 1) || (image.height & image.height - 1);
			var texture = this._textureDictionary[image._storeID];

			gl.activeTexture(gl.TEXTURE0 + texture._activeIndex);
			gl.bindTexture(gl.TEXTURE_2D, texture);

			texture.isPOT = !isNPOT;
			this.setTextureParams(gl, texture.isPOT);

			try {
				gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
				gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
			} catch (e) {
				var errString = "\nAn error has occurred. This is most likely due to security restrictions on WebGL images with local or cross-domain origins";
				if (console.error) {
					//TODO: LM: I recommend putting this into a log function internally, since you do it so often, and each is implemented differently.
					console.error(errString);
					console.error(e);
				} else if (console) {
					console.log(errString);
					console.log(e);
				}
			}
			gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);

			if (image._invalid !== undefined) { image._invalid = false; } // only adjust what is tracking this data

			texture.width = image.width;
			texture.height = image.height;

			if (isNPOT && this._antialias) {
				console.warn("NPOT(Non Power of Two) Texture w/ antialias on: " + image.src);
			}
			if (image.width > gl.MAX_TEXTURE_SIZE || image.height > gl.MAX_TEXTURE_SIZE) {
				console && console.error("Oversized Texture: " + image.width + "x" + image.height + " vs " + gl.MAX_TEXTURE_SIZE + "max");
			}
		};

		_insertTextureInBatch(gl, texture) {
			var image;
			if (this._batchTextures[texture._activeIndex] !== texture) {	// if it wasn't used last batch
				// we've got to find it a a spot.
				var found = -1;
				var start = (this._lastTextureInsert + 1) % this._batchTextureCount;
				var look = start;
				do {
					if (this._batchTextures[look]._batchID !== this._batchID) {
						found = look;
						break;
					}
					look = (look + 1) % this._batchTextureCount;
				} while (look !== start);

				// we couldn't find anywhere for it go, meaning we're maxed out
				if (found === -1) {
					this.batchReason = "textureOverflow";
					this._renderBatch();		// <------------------------------------------------------------------------
					found = start; //TODO: how do we optimize this to be smarter?
				}

				// lets put it into that spot
				this._batchTextures[found] = texture;
				texture._activeIndex = found;
				image = texture._imageData && texture._imageData[0]; // first come first served, potentially problematic
				if (image && ((image._invalid === undefined && image._isCanvas) || image._invalid)) {
					this._updateTextureImageData(gl, image);
				} else {
					// probably redundant, confirm functionality then remove from codebase
					//gl.activeTexture(gl.TEXTURE0 + found);
					//gl.bindTexture(gl.TEXTURE_2D, texture);
					//this.setTextureParams(gl);
				}
				this._lastTextureInsert = found;

			} else if (texture._drawID !== this._drawID) {	// being active from previous draws doesn't mean up to date
				image = texture._imageData && texture._imageData[0];
				if (image && ((image._invalid === undefined && image._isCanvas) || image._invalid)) {
					this._updateTextureImageData(gl, image);
				}
			}

			texture._drawID = this._drawID;
			texture._batchID = this._batchID;
		};

		_killTextureObject(texture) {
			if (!texture) { return; }
			var gl = this._webGLContext;

			// remove linkage
			if (texture._storeID !== undefined && texture._storeID >= 0) {
				this._textureDictionary[texture._storeID] = undefined;
				for (var n in this._textureIDs) {
					if (this._textureIDs[n] === texture._storeID) { delete this._textureIDs[n]; }
				}
				var data = texture._imageData;
				if (data) {
					for (var i = data.length - 1; i >= 0; i--) { data[i]._storeID = undefined; }
				}
				texture._imageData = texture._storeID = undefined;
			}

			// make sure to drop it out of an active slot
			if (texture._activeIndex !== undefined && this._batchTextures[texture._activeIndex] === texture) {
				this._batchTextures[texture._activeIndex] = this._baseTextures[texture._activeIndex];
			}

			// remove buffers if present
			try {
				if (texture._frameBuffer) { gl.deleteFramebuffer(texture._frameBuffer); }
				texture._frameBuffer = undefined;
			} catch (e) {
				/* suppress delete errors because it's already gone or didn't need deleting probably */
				console.log(e);
			}

			// remove entry
			try {
				gl.deleteTexture(texture);
			} catch (e) {
				/* suppress delete errors because it's already gone or didn't need deleting probably */
				console.log(e);
			}
		};

		_setCoverMixShaderParams(gl, stage, shaderProgram) {
			gl.uniform1i(
				gl.getUniformLocation(shaderProgram, "uMixSampler"),
				1
			);
		};

		_updateRenderMode(newMode: string) {
			if (newMode === null || newMode === undefined) { newMode = "source-over"; }

			var blendSrc = StageGL.BLEND_SOURCES[newMode];
			if (blendSrc === undefined) {
				console.log("Unknown compositeOperation [" + newMode + "], reverting to default");
				blendSrc = StageGL.BLEND_SOURCES[newMode = "source-over"];
			}

			if (this._renderMode === newMode) { return; }

			var gl = this._webGLContext;
			var shaderData = this._builtShaders[newMode];
			if (shaderData === undefined) {
				try {
					shaderData = this._builtShaders[newMode] = {
						eqRGB: gl[blendSrc.eqRGB || "FUNC_ADD"],
						srcRGB: gl[blendSrc.srcRGB || "ONE"],
						dstRGB: gl[blendSrc.dstRGB || "ONE_MINUS_SRC_ALPHA"],
						eqA: gl[blendSrc.eqA || "FUNC_ADD"],
						srcA: gl[blendSrc.srcA || "ONE"],
						dstA: gl[blendSrc.dstA || "ONE_MINUS_SRC_ALPHA"],
						immediate: blendSrc.shader !== undefined,
						shader: (blendSrc.shader || this._builtShaders["source-over"] === undefined) ?
							this._fetchShaderProgram(
								true, undefined, blendSrc.shader,
								this._setCoverMixShaderParams
							) : this._builtShaders["source-over"].shader // re-use source-over when we don't need a new shader
					};
					if (blendSrc.shader) { shaderData.shader._name = newMode; }
				} catch (e) {
					this._builtShaders[newMode] = undefined;
					console && console.log("SHADER SWITCH FAILURE", e);
					return;
				}
			}

			if (shaderData.immediate) {
				console.log("Illegal compositeOperation [" + newMode + "] due to StageGL.directDraw = true, reverting to default");
				return;
			}

			gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer);

			this.batchReason = "shaderSwap";
			this._renderBatch();		// <--------------------------------------------------------------------------------

			this._renderMode = newMode;
			this._immediateRender = shaderData.immediate;
			gl.blendEquationSeparate(shaderData.eqRGB, shaderData.eqA);
			gl.blendFuncSeparate(shaderData.srcRGB, shaderData.dstRGB, shaderData.srcA, shaderData.dstA);
		};

		_drawContent(content: Container) {
			var gl = this._webGLContext;

			this._activeShader = this._mainShader;

			gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer);
			if (this._batchTextureOutput._frameBuffer !== null) {
				gl.clear(gl.COLOR_BUFFER_BIT);
			}

			this._appendToBatch(content, new createjs.Matrix2D(), this.alpha);

			this.batchReason = "contentEnd";
			this._renderBatch();
		};


		_drawCover(out, dst, srcFilter?) {
			var gl = this._webGLContext;

			gl.bindFramebuffer(gl.FRAMEBUFFER, out);
			if (out !== null) { gl.clear(gl.COLOR_BUFFER_BIT); }

			gl.activeTexture(gl.TEXTURE0);
			gl.bindTexture(gl.TEXTURE_2D, dst);
			this.setTextureParams(gl);

			if (srcFilter instanceof WebGLTexture) {
				gl.activeTexture(gl.TEXTURE1);
				gl.bindTexture(gl.TEXTURE_2D, srcFilter);
				this.setTextureParams(gl);
			} else if (srcFilter !== undefined) {
				console.log("Unknown data handed to function: ", srcFilter);
			}
			this._activeShader = this._builtShaders[this._renderMode].shader;

			this._renderCover();
		};

		_alignTargetToCache(target, manager) {
			if (manager._counterMatrix === null) {
				manager._counterMatrix = target.getMatrix();
			} else {
				target.getMatrix(manager._counterMatrix)
			}

			var mtx = manager._counterMatrix;
			mtx.scale(1 / manager.scale, 1 / manager.scale);
			mtx = mtx.invert();
			mtx.translate(-manager.offX / manager.scale * target.scaleX, -manager.offY / manager.scale * target.scaleY);

			return mtx;
		};

		_appendToBatch(container: Container, concatMtx: Matrix2D, concatAlpha: number) {
			var gl = this._webGLContext;

			// sort out shared properties
			var cMtx = container._glMtx;
			cMtx.copy(concatMtx);
			if (container.transformMatrix !== null) {
				cMtx.appendMatrix(container.transformMatrix);
			} else {
				cMtx.appendTransform(
					container.x, container.y,
					container.scaleX, container.scaleY,
					container.rotation, container.skewX, container.skewY,
					container.regX, container.regY
				);
			}

			var previousRenderMode = this._renderMode;

			// sub components of figuring out the position an object holds
			var subL, subT, subR, subB;

			// actually apply its data to the buffers
			var l = container.children.length;
			for (var i = 0; i < l; i++) {
				var item = container.children[i];

				if (!(item.visible && concatAlpha > 0.0035)) {
					continue;
				}
				var itemAlpha = item.alpha;

				item._updateState();

				if ((item as Container).children) {
					this._appendToBatch((item as Container), cMtx, itemAlpha * concatAlpha);
					continue;
				}

				// check for overflowing batch, if yes then force a render
				if (this._batchVertexCount + StageGL.INDICIES_PER_CARD > this._maxBatchVertexCount) {
					this.batchReason = "vertexOverflow";
					this._renderBatch();					// <------------------------------------------------------------
				}

				// keep track of concatenated position
				var iMtx = item._glMtx;
				iMtx.copy(cMtx);
				if (item.transformMatrix) {
					iMtx.appendMatrix(item.transformMatrix);
				} else {
					iMtx.appendTransform(
						item.x, item.y,
						item.scaleX, item.scaleY,
						item.rotation, item.skewX, item.skewY,
						item.regX, item.regY
					);
				}

				var uvRect, texIndex, image, frame, texture, src;

				// get the image data, or abort if not present

				if (item._webGLRenderStyle === 1) {// SPRITE
					frame = (item as Sprite).spriteSheet.getFrame((item as Sprite).currentFrame);
					if (frame === null) { continue; }
					image = frame.image;
					// MISC (DOM objects render themselves later)
				} else {
					continue;
				}
				if (!image) {
					continue;
				}

				var uvs = this._uvs;
				var vertices = this._vertices;
				var texI = this._indices;
				var alphas = this._alphas;

				// calculate texture
				if (image._storeID === undefined) {
					// this texture is new to us so load it and add it to the batch
					texture = this._loadTextureImage(gl, image);
				} else {
					// fetch the texture (render textures know how to look themselves up to simplify this logic)
					texture = this._textureDictionary[image._storeID];

					if (!texture) { //TODO: this should really not occur but has due to bugs, hopefully this can be removed eventually
						console.log("Image source should not be lookup a non existent texture, please report a bug.");
						continue;
					}

					// put it in the batch if needed
					if (texture._batchID !== this._batchID) {
						this._insertTextureInBatch(gl, texture);
					}
				}
				texIndex = texture._activeIndex;
				image._drawID = this._drawID;

				if (item._webGLRenderStyle === 1) {
					var rect = frame.rect;

					// calculate uvs
					uvRect = frame.uvRect;
					if (!uvRect) {
						uvRect = StageGL.buildUVRects((item as Sprite).spriteSheet, (item as Sprite).currentFrame, false);
					}

					// calculate vertices
					subL = -frame.regX; subT = -frame.regY;
					subR = rect.width - frame.regX; subB = rect.height - frame.regY;
				}

				// These must be calculated here else a forced draw might happen after they're set
				var offV1 = this._batchVertexCount;					// offset for 1 component vectors
				var offV2 = offV1 * 2;								// offset for 2 component vectors

				//DHG: See Matrix2D.transformPoint for why this math specifically
				// apply vertices
				vertices[offV2] = subL * iMtx.a + subT * iMtx.c + iMtx.tx; vertices[offV2 + 1] = subL * iMtx.b + subT * iMtx.d + iMtx.ty;
				vertices[offV2 + 2] = subL * iMtx.a + subB * iMtx.c + iMtx.tx; vertices[offV2 + 3] = subL * iMtx.b + subB * iMtx.d + iMtx.ty;
				vertices[offV2 + 4] = subR * iMtx.a + subT * iMtx.c + iMtx.tx; vertices[offV2 + 5] = subR * iMtx.b + subT * iMtx.d + iMtx.ty;
				vertices[offV2 + 6] = vertices[offV2 + 2]; vertices[offV2 + 7] = vertices[offV2 + 3];
				vertices[offV2 + 8] = vertices[offV2 + 4]; vertices[offV2 + 9] = vertices[offV2 + 5];
				vertices[offV2 + 10] = subR * iMtx.a + subB * iMtx.c + iMtx.tx; vertices[offV2 + 11] = subR * iMtx.b + subB * iMtx.d + iMtx.ty;

				// apply uvs
				uvs[offV2] = uvRect.l; uvs[offV2 + 1] = uvRect.t;
				uvs[offV2 + 2] = uvRect.l; uvs[offV2 + 3] = uvRect.b;
				uvs[offV2 + 4] = uvRect.r; uvs[offV2 + 5] = uvRect.t;
				uvs[offV2 + 6] = uvRect.l; uvs[offV2 + 7] = uvRect.b;
				uvs[offV2 + 8] = uvRect.r; uvs[offV2 + 9] = uvRect.t;
				uvs[offV2 + 10] = uvRect.r; uvs[offV2 + 11] = uvRect.b;

				// apply texture
				texI[offV1] = texI[offV1 + 1] = texI[offV1 + 2] = texI[offV1 + 3] = texI[offV1 + 4] = texI[offV1 + 5] = texIndex;

				// apply alpha
				alphas[offV1] = alphas[offV1 + 1] = alphas[offV1 + 2] = alphas[offV1 + 3] = alphas[offV1 + 4] = alphas[offV1 + 5] = itemAlpha * concatAlpha;

				this._batchVertexCount += StageGL.INDICIES_PER_CARD;

				if (this._immediateRender) {
					this._immediateBatchRender();
				}
			}

			if (this._renderMode !== previousRenderMode) {
				this._updateRenderMode(previousRenderMode);
			}
		};

		_immediateBatchRender() {
			var gl = this._webGLContext;

			if (this._batchTextureConcat === null) {
				this._batchTextureConcat = this.getRenderBufferTexture(this._viewportWidth, this._viewportHeight);
			} else {
				this.resizeTexture(this._batchTextureConcat, this._viewportWidth, this._viewportHeight);
				gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureConcat._frameBuffer);
				gl.clear(gl.COLOR_BUFFER_BIT);
			}
			if (this._batchTextureTemp === null) {
				this._batchTextureTemp = this.getRenderBufferTexture(this._viewportWidth, this._viewportHeight);
				gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureTemp._frameBuffer);
			} else {
				this.resizeTexture(this._batchTextureTemp, this._viewportWidth, this._viewportHeight);
				gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureTemp._frameBuffer);
				gl.clear(gl.COLOR_BUFFER_BIT);
			}

			var swap = this._batchTextureOutput;
			this._batchTextureOutput = this._batchTextureConcat;
			this._batchTextureConcat = swap;

			this._activeShader = this._mainShader;
			this.batchReason = "immediatePrep";
			this._renderBatch();//<-----------------------------------------------------------------------------------------

			this.batchReason = "immediateResults";
			this._drawCover(this._batchTextureOutput._frameBuffer, this._batchTextureConcat, this._batchTextureTemp);

			gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer);
		};

		_renderBatch() {
			if (this._batchVertexCount <= 0) { return; }	// prevents error logs on stages filled with un-renederable content.
			var gl = this._webGLContext;

			console.log("Batch[" + this._drawID + ":" + this._batchID + "] : " + this.batchReason);
			var shaderProgram = this._activeShader;
			var vertexPositionBuffer = this._vertexPositionBuffer;
			var textureIndexBuffer = this._textureIndexBuffer;
			var uvPositionBuffer = this._uvPositionBuffer;
			var alphaBuffer = this._alphaBuffer;

			gl.useProgram(shaderProgram);

			gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
			gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._vertices);

			gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer);
			gl.vertexAttribPointer(shaderProgram.textureIndexAttribute, textureIndexBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._indices);

			gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
			gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._uvs);

			gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer);
			gl.vertexAttribPointer(shaderProgram.alphaAttribute, alphaBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._alphas);

			gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, gl.FALSE, this._projectionMatrix);

			for (var i = 0; i < this._batchTextureCount; i++) {
				gl.activeTexture(gl.TEXTURE0 + i);
				gl.bindTexture(gl.TEXTURE_2D, this._batchTextures[i]);
			}

			gl.drawArrays(gl.TRIANGLES, 0, this._batchVertexCount);

			this._batchVertexCount = 0;
			this._batchID++;
		};

		_renderCover() {
			var gl = this._webGLContext;
			console.log("Cover[" + this._drawID + ":" + this._batchID + "] : " + this.batchReason);
			var shaderProgram = this._activeShader;
			var vertexPositionBuffer = this._vertexPositionBuffer;
			var uvPositionBuffer = this._uvPositionBuffer;

			gl.useProgram(shaderProgram);

			gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
			gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.COVER_VERT);
			gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer);
			gl.vertexAttribPointer(shaderProgram.uvPositionAttribute, uvPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
			gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.COVER_UV);

			gl.uniform1i(shaderProgram.samplerUniform, 0);

			gl.drawArrays(gl.TRIANGLES, 0, StageGL.INDICIES_PER_CARD);
			this._batchID++; // while this isn't a batch, this fixes issues with expected textures in expected places
		};
	}
}

